package roborally;

import java.util.*;
import be.kuleuven.cs.som.annotate.*;
import Exceptions.ElementPlacementError;
import Exceptions.IllegalCoordinateException;
import Exceptions.IllegalDimensionException;
import Exceptions.MergeError;
import Exceptions.TerminationError;

/**
 * @author Jef Keijers/Bachelor of Science: Informatics
 * @version 1.0
 */
public class Board extends Terminatable { 
	
	/**
	 * Constructs a new board.
	 * @param 	width
	 * 			The width the board will have.
	 * @param 	height
	 * 			The height the board will have.
	 * @throws 	IllegalDimensionException
	 * 			| if ((canHaveAsWidth(width)!= true) || (canHaveAsHeight(height) != true )) then throw new IllegalDimensionException()
	 * @post 	| new.getWidth() = width
	 * @post 	| new.getHeight() = height
	 * @post 	| new.getPositionMap() = new HashMap<Position,LinkedList<Element>>()
	 */
	public Board(long width, long height) throws IllegalDimensionException {
		if ((canHaveAsWidth(width)) && (canHaveAsHeight(height))) {
			this.width = width;		
			this.height= height;		
			hashmap = new HashMap <Position,LinkedList<Element>>();
		} else { 
			throw new IllegalDimensionException();
		}
	}

	/**
	 * Returns the height of the board.
	 */
	@Basic
	public long getHeight() {
		return height;
	}

	/**
	 * Returns the width of the board.
	 */
	@Basic	
	public long getWidth() {
		return width;
	}
	
	/**
	 * Returns the maxHeight of the board.
	 */
	@Basic @Immutable
	public static long getMaxHeight() {
		return maxHeight;				
	}
	
	/**
	 * Returns the maxWidth of the board.
	 */
	@Basic @Immutable
	public static long getMaxWidth() {
		return maxWidth;				
	}	

	/**
	 * Returns the hashmap of the board.
	 */
	@Basic
	public HashMap<Position,LinkedList<Element>> getPositionMap() {
		return hashmap;
	}
	
	/**
	 * @param 	height
	 * 			The height to control if it's valid.
	 * @return | if (!(height>getMaxHeight())&& !(height<0)) then result == true
	 */
	private boolean canHaveAsHeight(long height){
		if (!(height> getMaxHeight()) && !(height<0)) {
			return true;
		}
		return false;
	}
	
	/**
	 * @param 	width
	 * 			The width to control if it's valid.
	 * @return | if ((widthgetMaxWidth())&& (width=>0)) then result == true
	 */
	private boolean canHaveAsWidth(long width){
		if (!(width> getMaxWidth()) && !(width<0)) 
			return true;
		return false;
	}	
	
	private final long width;
	
	private final long height;
	
	private static long maxHeight=Long.MAX_VALUE;
	
	private static long maxWidth=Long.MAX_VALUE;
	
	private HashMap<Position, LinkedList<Element>> hashmap;
	
	/**
	 * @param 	element
	 * @param 	position
	 * @param 	board
	 * @throws 	ElementPlacementError 
	 * 			|this.setBoard(this)
	 * @throws 	IllegalCoordinateException
	 * 			|this.setPosition(position)
	 * @effect	
	 * 			|element.setBoard(this)
	 * @effect	
	 * 			|element.setPosition(position)
	 * @effect	
	 * 			|this.getPositionMap().put(position,list)
	 */
	public boolean putElementOnPosition(Element element,Position position) throws  IllegalCoordinateException, ElementPlacementError {
		boolean test=true;		
		LinkedList <Element> list=this.getPositionMap().get(position);		
		if(list==null) {
			list=new LinkedList<Element>();
			this.getPositionMap().put(position, list);
		} else {
			for (Element element2:list) {									
				if (!(element.canSharePositionWith(element2))) {
					test=false;
					break;			
				}
			}
		}	
		if (test) {
			element.setBoard(this);
			element.setPosition(position);
			list.add(element);
			return true;
		}		
		return false;
	}

	/**
	 * @param 	element
	 * @param 	position
	 * @throws 	TerminationError
	 * 			|if((list==null || !list.remove(element)))
	 * @effect	
	 * 			|element.terminate()
	 * @effect	
	 * 			|this.getPositionMap().remove(position)
	 */
	@Raw
	public void terminateFromBoard(Element element,Position position) throws TerminationError {
		LinkedList <Element> list = this.getPositionMap().get(position);
		if((list==null || !list.remove(element))){
			throw new TerminationError();
		}		
		if (list.isEmpty())
			this.getPositionMap().remove(position);
		element.terminate();
	}
	
	/**
	 * @effect	
	 * 			|this.setIsTerminatedToTrue()
	 * @effect
	 * 			|element.terminate()
	 * @post	
	 * 			|new.hashmap==null
	 */
	@Override @Raw
	public void terminate(){
		Set<Position> positions = this.getPositionMap().keySet();
		for (Position position:positions){
			LinkedList<Element> values = this.getPositionMap().get(position);
			for (Element element: values) {
				element.terminate();
			}
			values.clear();
		}
		this.hashmap = null;
		this.setIsTerminatedToTrue();
	}
	
	/**
	 * @param 	robot
	 * @param 	battery
	 * @param 	position
	 * @throws 	TerminationError
	 * @effect 	
	 * 			|this.terminateFromBoard(item,position);		
	 * @post	
	 * 			|robot.getPossessionList().add(item)
	 */
	@Raw
	public void awayFromBoardToPossessions(Robot robot,Item item,Position position) throws TerminationError {
		this.terminateFromBoard(item,position);		
		robot.getPossessionList().add(item);
	}


	/**
	 * @param 	board2
	 * @throws	MergeError
	 * 			|if(!this.isCompatibleWith(board2)) then throw new MergeError()
	 * @throws 	ElementPlacementError
	 * 			|this.putElementOnPosition(elements,positions);
	 * @throws 	IllegalCoordinateException 
	 * @effect	
	 * 			|this.putElementOnPosition(elements,positions);
	 * 
	 */
	@Raw
	public void merge(Board board2) throws MergeError, ElementPlacementError, IllegalCoordinateException {
		if (this.isCompatibleWith(board2)) {
			for (Position positions:board2.getPositionMap().keySet()){
				for(Element elements:board2.getPositionMap().get(positions)){
					this.putElementOnPosition(elements,positions);
				}
			}
			board2=null; // Terminate mag hier niet gebruikt worden!
		} else throw new MergeError();
	}
	
	/**
	 * @param 	other
	 * @return | if ((getHeight() == other.getHeight()) && (getWidth() == other.getWidth())) then result == true
	 */
	private boolean isCompatibleWith(Board other) {
		if ((other.getHeight()==this.getHeight()) && (other.getWidth()==this.getWidth())) {
			return true;
		}
		return false;
	}
}

